﻿/*
/// 
/// Portions Copyright 1999-2004 Carnegie Mellon University.  
/// Portions Copyright 2004 Sun Microsystems, Inc.  
/// Portions Copyright 2004 Mitsubishi Electric Research Laboratories.
/// All Rights Reserved.  Use is subject to license terms.
/// 
/// See the file "license.terms" for information on usage and
/// redistribution of this file, and for a DISCLAIMER OF ALL 
/// WARRANTIES.
 *
 */


using System;
using System.Collections.Generic;
using System.Text;
using Decoder;
using Instrumentation;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Util.Props;
using Common;

namespace Recognizer
{
    public class Recognizer : IConfigurable, IResultProducer
    {
        /// <summary>
        /// The property for the decoder to be used by this recognizer.
        /// </summary>
        [S4Component(type=typeof(Decoder.Decoder))]
        public static String PROP_DECODER = "decoder";

        /// <summary>
        /// The property for the set of monitors for this recognizer 
        /// </summary>
        [S4ComponentList(type = typeof(Instrumentation.IMonitor))]
        public static String PROP_MONITORS = "monitors";
        /// <summary>
        /// Defines the possible states of the recognizer.
        /// </summary>
        public enum State { DEALLOCATED, ALLOCATING, ALLOCATED, READY, RECOGNIZING, DEALLOCATING, ERROR };

        private State currentState = State.DEALLOCATED;
        private String name = String.Empty;

        private Decoder.Decoder decoder = null;
        private List<Instrumentation.IMonitor> monitors = null;

        private List<IStateListener> stateListeners = new List<IStateListener>();


        public Recognizer()
        {

        }

        public Recognizer(Decoder.Decoder decoder, List<IMonitor> monitors)
        {
            this.decoder = decoder;
            this.monitors = monitors;
        }

        /// <summary>
        /// Performs recognition for the given number of input frames, or until a 'final' result is generated. This method
        /// should only be called when the recognizer is in the <code>allocated</code> state.
        /// </summary>
        /// <param name="referenceText">referenceText what was actually spoken</param>
        /// <returns>a recognition result</returns>
        public IResult recognize(String referenceText)
        {

            IResult result = null;
            checkState(State.READY);
            try
            {
                setState(State.RECOGNIZING);
                result = decoder.decode(referenceText);
            }
            catch (IllegalStateException ex)
            {
                throw new IllegalStateException(ex.Message);
            }
            finally
            {
                setState(State.READY);
            }

            return result;
        }

        /// <summary>
        /// Performs recognition for the given number of input frames, or until a 'final' result is generated. This method
        /// should only be called when the recognizer is in the <code>allocated</code> state.
        /// </summary>
        /// <returns></returns>
        public IResult recognize()
        {
            return recognize(null);
        }

        /// <summary>
        /// Checks to ensure that the recognizer is in the given state.
        /// </summary>
        /// <param name="desiredState">desiredState the state that the recognizer should be in</param>
        private void checkState(State desiredState)
        {
            if (currentState != desiredState)
            {
                throw new IllegalStateException("Expected state " + desiredState
                        + " actual state " + currentState);
            }
        }
        /// <summary>
        /// sets the current state
        /// </summary>
        /// <param name="newState"> newState the new state</param>
        private void setState(State newState)
        {
            currentState = newState;
            foreach (var item in stateListeners)
            {
                item.statusChanged(currentState);
            }
        }

        /// <summary>
        /// Allocate the resources needed for the recognizer. Note this method make take some time to complete. This method
        /// should only be called when the recognizer is in the deallocated state.
        /// throws IllegalStateException if the recognizer is not in the DEALLOCATED state
        /// </summary>
        public void allocate()
        {
            try
            {
                checkState(State.DEALLOCATED);
                setState(State.ALLOCATING);
                decoder.allocate();
                setState(State.ALLOCATED);
                setState(State.READY);
            }
            catch (IllegalStateException ex)
            {
                throw new IllegalStateException(ex.Message);
            }
        }
        /// <summary>
        /// Deallocates the recognizer. This method should only be called if the recognizer is in the allocated
        /// state.
        /// throws IllegalStateException if the recognizer is not in the ALLOCATED state
        /// </summary>
        public void deallocate()
        {
            try
            {
                checkState(State.READY);
                setState(State.DEALLOCATING);
                decoder.deallocate();
                setState(State.DEALLOCATED);
            }
            catch (IllegalStateException ex)
            {
                throw new IllegalStateException(ex.Message);
            }
        }
        /// <summary>
        /// Retrieves the recognizer state. This method can be called in any state.
        /// </summary>
        /// <returns></returns>
        public State getState()
        {
            return currentState;
        }

        /// <summary>
        /// Resets the monitors monitoring this recognizer
        /// </summary>
        public void resetMonitors()
        {
            foreach (IMonitor listener in monitors)
            {
                if (listener is Instrumentation.IResetable)
                    ((IResetable)listener).reset();
            }
        }

        /// <summary>
        /// Adds a result listener to this recognizer. A result listener is called whenever a new result is generated by the
        /// recognizer. This method can be called in any state.
        /// </summary>
        /// <param name="resultListener">resultListener the listener to add</param>
        public void addResultListener(IResultListener resultListener)
        {
            decoder.addResultListener(resultListener);
        }

        /// <summary>
        /// Adds a status listener to this recognizer. The status listener is called whenever the status of the recognizer
        /// changes. This method can be called in any state.
        /// </summary>
        /// <param name="stateListener">stateListener the listener to add</param>
        public void addStateListener(IStateListener stateListener)
        {
            stateListeners.Add(stateListener);
        }

        /// <summary>
        /// Removes a previously added result listener. This method can be called in any state.
        /// </summary>
        /// <param name="resultListener">resultListener the listener to remove</param>
        public void removeResultListener(IResultListener resultListener)
        {
            decoder.removeResultListener(resultListener);
        }

        /// <summary>
        /// Removes a previously added state listener. This method can be called in any state.
        /// </summary>
        /// <param name="stateListener">stateListener the state listener to remove</param>
        public void removeStateListener(IStateListener stateListener)
        {
            ///It's working but change the order of the collection. (And I didn't checked it in multi threads state).
            lock (stateListeners)
            {
                stateListeners.Remove(stateListener);
            }
        }

        override public String ToString()
        {
            return "Recognizer: " + name + " State: " + currentState;
        }

        void IConfigurable.newProperties(PropertySheet ps)
        {
            decoder = (Decoder.Decoder)ps.getComponent(PROP_DECODER);
            monitors = ps.getComponentList<IMonitor>(PROP_MONITORS);

            name = ps.InstanceName;
        }
    }
}
